<template>
  <div class="design-preview" ref="preview">  	
  	<!--显示的组件列表-->
    <ul class="design-item-list" ref="sortTable">    	
      <slot name="top"></slot>
      <li
        v-for="item in currentCompList"
        :key="item.cid"
        @click="bindSelectComp(item.cid)"
        class="J-sort-item"
      >
        <!--:show="item.cid == currentCid" 是即时生效的 之后即使改变页不影响-->
        <!--传入属性是所有的需要用到的属性 子组件根据需求选择性导入需要的属性-->
        <component
          :is="item.name"
          :show="item.cid == currentCid"
          :options="item.options"
          :cid="item.cid"
          :ref="'id_'+item.cid"
          :class="[{'activate' :item.cid == currentCid},'J-design-component-item']"
          :show-add-menu-group="item.cid == currAddGroup"
          :selection-goods-list="selectionGoodsList"
        >        
          <div slot="menu">
          	
          	<!--删除组件-->
            <div class="design-item-btn design-item-delete" @click.stop="bindShowRemoveConfirm($event,item)">
              <i class="el-icon-close" aria-hidden="true"></i>
            </div>

            <!--向上添加-->
            <div class="design-item-prepend">
              <div class="design-item-btn design-item-btn-add" @click.stop="bindPrependChild(item.cid)">
                <i class="el-icon-circle-plus" aria-hidden="true"></i>
              </div>
              <div class="design-item-btn-add-marker">
                <i class="marker-circle-left"></i>
                <div class="marker-circle-line"></div>
                <i class="marker-circle-right"></i>
              </div>
            </div>
            <!--向下添加-->
            <div class="design-item-append">
              <div
                class="design-item-btn design-item-btn-add"
                @click.stop="bindAppendChild(item.cid)"
              >
                <i class="el-icon-circle-plus" aria-hidden="true"></i>
              </div>
              <div class="design-item-btn-add-marker">
                <i class="marker-circle-left"></i>
                <div class="marker-circle-line"></div>
                <i class="marker-circle-right"></i>
              </div>
            </div>
                        
          </div>
          
          <div slot="addMenuGroup"
            :class="['design-item-editor', currInsertPos ? 'design-item-editor-bottom': '']"
          >
            <div class="design-preview-add-group" v-if="showCompGroup0.length">
              <p class="design-preview-add-group-title">基础组件</p>
              <div class="design-preview-add-group-list">
                <div
                  :class="['design-preview-add-group-btn' , {'design-preview-add-group-btn-disable' : !item.enableAdd}]"
                  v-for="item in showCompGroup0"
                  @mouseenter="bindShowTips($event , item)"
                  @mouseleave="bindHideTips()"
                  v-bind:key="item.name"
                  @click.stop="bindAddComp(item)"
                >
                  <span>{{item.title}}</span>
                </div>
              </div>
            </div>
            <div class="design-preview-add-group" v-if="showCompGroup1.length ">
              <p class="design-preview-add-group-title">营销组件</p>
              <div class="design-preview-add-group-list">
                <div
                  :class="['design-preview-add-group-btn' , {'design-preview-add-group-btn-disable' : !item.enableAdd}]"
                  v-for="item in showCompGroup1"
                  @mouseenter="bindShowTips($event , item)"
                  @mouseleave="bindHideTips()"
                  v-bind:key="item.name"
                  @click.stop="bindAddComp(item)"
                >
                  <span>{{item.title}}</span>
                </div>
              </div>
            </div>
            <div class="design-preview-add-group" v-if="showCompGroup2.length ">
              <p class="design-preview-add-group-title">其他</p>
              <div class="design-preview-add-group-list">
                <div
                  :class="['design-preview-add-group-btn' , {'design-preview-add-group-btn-disable' : !item.enableAdd}]"
                  v-for="item in showCompGroup2"
                  @mouseenter="bindShowTips($event , item)"
                  @mouseleave="bindHideTips()"
                  v-bind:key="item.name"
                  @click.stop="bindAddComp(item)"
                >
                  <span>{{item.title}}</span>
                </div>
              </div>
            </div>
          </div>
        
        </component>
    
    </li>
    </ul>
    
    
    
    <!--下方控件-->
    <div class="design-preview-add">
      <div class="design-preview-add-group" v-if="showCompGroup0.length ">
        <p class="design-preview-add-group-title">基础组件</p>
        <div class="design-preview-add-group-list">
          <div
            :class="['design-preview-add-group-btn' , {'design-preview-add-group-btn-disable' : !item.enableAdd}]"
            v-for="item in showCompGroup0"
            v-bind:key="item.name"
            @click="bindAddComp(item)"
            @mouseenter="bindShowTips($event , item)"
            @mouseleave="bindHideTips()"
          >
            <span>{{item.title}}</span>
          </div>
        </div>
      </div>
      
      
      <div class="design-preview-add-group" v-if="showCompGroup1.length ">
        <p class="design-preview-add-group-title">营销组件</p>
        <div class="design-preview-add-group-list">
          <div
            :class="['design-preview-add-group-btn' , {'design-preview-add-group-btn-disable' : !item.enableAdd}]"
            v-for="item in showCompGroup1"
            v-bind:key="item.name"
            @mouseenter="bindShowTips($event , item)"
            @mouseleave="bindHideTips()"
            @click="bindAddComp(item)"
          >
            <span>{{item.title}}</span>
          </div>
        </div>
      </div>
      <div class="design-preview-add-group" v-if="showCompGroup2.length">
        <p class="design-preview-add-group-title">其他</p>
        <div class="design-preview-add-group-list">
          <div
            :class="['design-preview-add-group-btn' , {'design-preview-add-group-btn-disable' : !item.enableAdd}]"
            v-for="item in showCompGroup2"
            v-bind:key="item.name"
            @mouseenter="bindShowTips($event , item)"
            @mouseleave="bindHideTips()"
            @click="bindAddComp(item)"
          >
            <span>{{item.title}}</span>
          </div>
        </div>
      </div>
    </div>
 
 </div>
</template>
<script>
// 关于jquery参数的解释 https://www.runoob.com/jqueryui/api-sortable.html#option-items
import "../../assets/js/jquery-ui.min.js";
import $ from "jquery";

// compSettingList 是一个数组 格式为[{},{}] 每一项都是对应组件的一些需要用到的属性集合 作用：分组-渲染等
// components 所有的组件对象的集合 对象格式为{"key":{},"key":{}} 对应vue的 components属性 用来注册所有组件
import {
  compSettingList,
  components
} from "./design-preview-components-loader";


export default {
  name: "market-design-preview", // 组件注册的名字 前端模板用到的父组件的外层元素标签
  components: components,  // 注册所有组件
  props: {
    // 绑定的组件列表
    // ['cube' , 'goods' , 'image' , 
    // 'line' ,'whitespace','notice','offlineShop','richText' , 
    // 'search' , 'shopBanner' ,'store' ,'text' ,'title' ]    
    showComponentList: {
      type: Array,
      default: []
    },
    selectionGoodsList: {
      type: Array,
      default: ()=>{
        return []
      }
    }
  },
  data() {
    return {    
      currentCompList: [], // 当前显示在页面的组件列表
      currentCid: -1,      // 用于记录当前组件的序号
      allCompList: compSettingList, // 所有组件的配置属性
      gloToken: 1,          // 用来记录当前组件的序号 从1开始
      currAddGroup: -1,     // 用来控制右侧编辑层 addMenuGroup这个插槽（编辑层添加组件的功能）是否显示   
      currInsertIndex: -1,  // 用来控制插入组件的位置，只有点击'向上/向下' 按钮后 这个值才会发生改变
      currInsertPos: 0,     // 点击向下添加组件的时候重置右侧编辑层的位置
      showCompGroup0: [], // 控件第一组
      showCompGroup1: [], // 控件第二组
      showCompGroup2: []  // 控件第三组 
    };
  },
  computed: {},
  watch: {
    currentCid(value) {
      // this.$emit("change-cid", value);
    }
  },
  created() {
    var self = this,
    // 判断传入的组件是否存在于组件列表中/如果没传任何值(空数组)或者传入的值错误 则默认所有组件都显示
	  verifyInSelectCompList = function(item) {
	    if (!self.showComponentList.length) return true;
	    if (self.showComponentList.indexOf(item.type) >= 0) return true;
	    return false;
	  };
	  // 根据组件的group属性分组
    this.allCompList.forEach(item => {
      if (item.group === 0 && verifyInSelectCompList(item)) {
        self.showCompGroup0.push(item);
      }
      if (item.group === 1 && verifyInSelectCompList(item)) {
        self.showCompGroup1.push(item);
      }
      if (item.group === 2 && verifyInSelectCompList(item)) {
        self.showCompGroup2.push(item);
      }
    });
  },
  mounted() {
  	// jqueryUi 拖拽配置 
    var elSort = $(this.$refs.sortTable),// 获取原生元素
    self = this, // vue实例 不多说了
    oldIndex = -1, // 记录拖拽前元素的下标  sortable.start记录  
    newIndex = -1; // 用来拖拽后元素的下标  sortable.stop记录   
    // 判断知否加载了 jquery ui
    if ($.fn.sortable == "undefined") {
      console.warn("jquery ui sortable is undefined");
      return;
    }
    elSort.sortable({
    	// 限制在指定的元素上开始排序（就是初始排序点）这个类在各个组件内部
      handle: ".design-item-controller-handle", 
      // 鼠标按下后排序开始前必须移动的距离，以像素计。（拖动多少像素后才开始移动）
      distance: 5,
      // 定义只能垂直方向上 拖拽
      axis: "y", 
      // 指定元素内的哪一个项目应是 sortable 这个类在各个组件内部
      items: ".J-sort-item", 
      // 当排序开始时触发该事件 ui.item 表示当前被拖拽的元素。 鼠标按下并且元素移动
      start: (event, ui) => {
        oldIndex = ui.item.index(".J-sort-item");
      },
      // 当排序停止时触发该事件 ui.item 表示当前被拖拽的元素。 鼠标弹起并且元素排序完成
      stop: (event, ui) => {
        newIndex = ui.item.index(".J-sort-item");        
        if (oldIndex == newIndex) return; // 表示元素在原来的位置
        // 数组交换 （元素交换）
        self.currentCompList.splice(
          newIndex,
          0,
          self.currentCompList.splice(oldIndex, 1)[0]
        );
      }
    });    
    var debRefreshHeight = this.debounce(function() {
      self.emitResize();
    }, 100);
    // self.$refs.preview 最外层元素  监听这个元素的内部元素的添加和删除事件
    $(self.$refs.preview)
      .on("DOMNodeInserted", debRefreshHeight)
      .on("DOMNodeRemoved", debRefreshHeight);      
  },
  methods: {
  	// 在添加或者删除元素的时候，保证父元素高度能够自适应
    debounce: function(func, wait, immediate) {
      var timeout, args, context, timestamp, result;
      // 这边的 this 还是Vue实例 因为还没有进入函数域层
      var _this = this; 
      // 保证在0.1S内只能点击一次控件 执行的是 emitResize这个方法
      var later = function() {        
        var last = _this.nowTime() - timestamp;           
        if (last < wait && last >= 0) {
          timeout = setTimeout(later, wait - last);
        } else {
          timeout = null;
          if (!immediate) {
            result = func.apply(context, args);
            if (!timeout) context = args = null;
          }
        }
      };
      return function() {
      	// 由于执行的方法为 $(self.$refs.preview).on()模式
      	// 这边的this指向已经改变  不在为 vue实例  为 $(self.$refs.preview) 这个元素
        context = this;  
        // arguments 关键字 为函数的参数集合
        args = arguments;        
        // vue实例的.nowTime()方法  返回一个当前时间戳
        timestamp = _this.nowTime();
        // 且运算 如果 immediate存在 就 =!timeout 不存在就为假
        var callNow = immediate && !timeout;
        
        // 执行 later函数
        if (!timeout) timeout = setTimeout(later, wait);
        
        // 第三个参数不存在的情况下执行
        if (callNow) {
          result = func.apply(context, args);
          context = args = null;
        }
        return result;
      };
    },
    // 返回当前时间戳
    nowTime: function() {
      return new Date().getTime();
    },
    // 重置父组件的高度
    emitResize() {
      var self = this;
      this.$nextTick(() => {     	
        var el = $(self.$refs.preview),  // 最外层元素
          elEditor = $(".design-item.activate .design-item-editor"),// 右边的编辑层
          previewHeight = el.outerHeight(),  // 返回最外层元素的外部高度（不包含margin）
          editorHeight = elEditor.outerHeight(),// 返回编辑层的外部高度
          previewOffset = el.offset(),// 返回最外层元素相对于文档的偏移 格式为{top:84,left:200}
          editorOffset = elEditor.offset(),// 返回编辑层相对于文档的偏移 格式为{top:123,left:539}
          computedHeight = previewHeight, // 绑定最外层元素的高度值
          gap = 0;  // 间隔值（高度差）高度差>0的话需要父组件的高度值对应的加上这个 插值
        // 公式  编辑层的top值  + 编辑层自身的高度   - (最外层元素的top + 最外层元素自身的高度)
        // 随着添加元素的增多  编辑层的top值会越来越大，最终 公式的值会>0        
        if (editorOffset) {
          gap =
            editorOffset.top +
            editorHeight -
            (previewOffset.top + previewHeight);
          computedHeight += gap > 0 ? gap : 0;
        }
        // 如果间隔值大于0 通知父组件增加相应的高度值
        if(gap>0){
	        // 通知父组件 高度增加相应的间隔值
	        self.$emit("preview-resize", computedHeight, gap);        	
        }
      });
    },    

    // 获取当前选中组件在显示列表中的位置
    getIndexFromCompList(cid) {
      let r = -1;
      $.each(this.currentCompList, (index, item) => {
        if (item.cid == cid) {
          r = index;
          return;
        }
      });
      return r;
    },
    // 添加组件
    bindAddComp(item) {       	
      if (!item.enableAdd) return; // 控件是否能点击      
      // 合并对象 不改变原来的item(之后对newItem做处理不会影响原来的item属性)      
      var newItem = $.extend(true, {}, item); 
      newItem.cid = this.gloToken++;// 注意赋值以后 this.gloToken才会+1 此时 newItem.cid 还是原来的值      
      // 这个值会在点击'向上向下'按钮 以后发生改变 用来确定组件添加到哪个位置
      if (this.currInsertIndex >= 0) {
      	// 参数1-规定添加项的位置  参数2-0代表不删除原来位置的项  参数3-newItem代表添加的项
        this.currentCompList.splice(this.currInsertIndex, 0, newItem);
      } else {
        this.currentCompList.push(newItem);
      }
      this.currInsertIndex = -1;  // 添加完成后 重置成-1（清除'向上/向下'按钮的影响）
      this.currAddGroup = -1;     // 添加完成后 重置成-1（让右侧编辑层出现的组件添加功能消失）
      this.currentCid = newItem.cid;  // 记录当前组件的序号
      this.verifyCompAddEnable(item); // 验证是否能添加组件 通过组件的max值确定
      this.emitResize(); // 重置父组件高度
    },   
   
   
    // 验证当前组件是否还能添加 针对组件的max值
    verifyCompAddEnable(comp) {
      var max = comp.max,      
        currentNum = 0;        
      if (max == 0) return true;
      currentNum = this.currentCompList.filter(item => {
        return item.type === comp.type;
      }).length;
      this.allCompList.filter(item => {
        return item.type == comp.type;
      })[0].enableAdd = currentNum < max;
    },
    // 鼠标移入添加组件按钮
    bindShowTips(event, item) { },
    // 鼠标离开添加组件按钮
    bindHideTips() {},

    // 选择组件
    bindSelectComp(index) {
      this.currAddGroup = -1;  // 让右侧编辑层的添加组件的功能消失
      // this.$emit("change-cid", index);  
      if (index == this.currentCid) return; // 点击了本身 返回
      this.currentCid = index; // 改变当前选中的值
      this.currInsertIndex = -1; // 清除'向上/向下'按钮的影响
      this.emitResize();  // 重置父组件高度
    },
    // 向下添加组件
    bindAppendChild(cid) {      
      this.currAddGroup = cid;  // 让 对应的右边编辑层中添加组件的功能插槽显示
      let index = this.getIndexFromCompList(cid); // 获取当前选中的组件的序号      
      this.currInsertIndex = index >= 0 ? index + 1 : -1; // 记录插入组件的位置
      this.currInsertPos = 1; // 重置右侧编辑层的位置
      this.currentCid = -1;  // 清除当前选中的组件的序号
    }, 
    // 向上添加组件
    bindPrependChild(cid) {
      this.currAddGroup = cid;  // 让 对应的右边编辑层中添加组件的功能插槽显示
      let index = this.getIndexFromCompList(cid);// 获取当前选中的组件的序号 
      this.currInsertIndex = index;  // 记录插入组件的位置
      this.currInsertPos = 0;  // 重置右侧编辑层的位置 
      this.currentCid = -1;    // 清除当前选中的组件的序号
    },
    // 弹出删除确认框
    bindShowRemoveConfirm(event, item) {      
      var self = this;
      this.$confirm("确定删除?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(() => {
          self.removeChild(item); // 删除组件
          this.$message({
            type: "success",
            message: "删除成功!"
          });
       }).catch(() => {});
    },
    // 删除组件
    removeChild(pendingItem) {
      let delIndex = -1;
      $.each(this.currentCompList, (index, item) => {
        if (pendingItem.cid == item.cid) {
          delIndex = index;
          return;
        }
      });
      delIndex >= 0 && this.currentCompList.splice(delIndex, 1);
      this.verifyCompAddEnable(pendingItem);
      this.emitResize();
    },
    // 获取所有数据
    getAllData() {
      var self = this,r = [];
      $.each(this.currentCompList, function(index, item) {
        let comp = self.$refs[`id_${item.cid}`],
          _curr = null;
        if (comp.length) {
          _curr = comp[0].getCurrentData();
          if (_curr) {
            r.push(_curr);
          } else {
            self.currentCid = item.cid;
            r = null;
            return;
          }
        }
      });
      return r;
    }   
  }
};
</script>